# 40-Pin Expansion Header **The Quectel Pi M1/L1** intelligent main control board provides a standard 40‑pin GPIO expansion header, which supports multiple peripheral interfaces including GPIO, I2C, SPI, UART, and PWM. The following section describes how to test the functionality of these interfaces. ```{image} images/image_QpJBbQE7yoGzwdxn2OMch2ULnTE.webp :width: 882px :height: 515px :align: center ``` # Pin Definition

Function3

Function2

Function1

L1 GPIO#

M1 GPIO#

Pin#

Pin#

M1 GPIO#

L1 GPIO#

Function1

Function2

Function3

VCC 3V3

1

2

VCC 5V

Common IIC SDA







GPIO_109(I2C1_SDA)

3

4

VCC 5V

Common IIC SCL







GPIO_110(I2C1_SCL)

5

6

GND










GPIO_60

GPIO_83

7

8

GPIO_69(UART1_TXD)

SPI1_SCLK

UART1_TX




GND

9

10

GPIO_70(UART1_RXD)

SPI1_CS_N

UART1_RX













GPIO_106

11

12

GPIO_98



















GPIO_31

13

14

GND




UART2_TX

SPI2_SCLK

GPIO_71

15

16

GPIO_84

PMU_GPIO3










VCC 3V3

17

18

GPIO_25

GPIO_32










I2C0_SCL

UART0_RTS

SPI0_MOSI

GPIO_1(SPI_MOSI)

19

20

GND

I2C0_SDA

UART0_CTS

SPI0_MISO

GPIO_0(SPI_MISO)

21

22

GPIO_80

SPI2_CS_N

UART2_RX







UART0_TX

SPI0_SCLK

GPIO_2(SPI_CLK)

23

24

GPIO_3(SPI_CE0)

SPI0_CS_N_0

UART0_RX




GND

25

26

GPIO_82(SPI_CE1)










I2C5_SDA

UART5_CTS

SPI5_MISO

GPIO_14(I2C0_SDA)

27

28

GPIO_15(I2C0_SCL)

SPI5_MOSI

UART5_RTS

I2C5_SCL

I2C1_SCL

UART1_RTS

SPI1_MOSI

GPIO_5

29

30

GND

I2C1_SDA

UART1_CTS

SPI1_MISO

GPIO_4

31

32

GPIO_67

PMU_GPIO8
















PWM

PMU_GPIO2(PWM)

PMU_GPIO8(PWM)

33

34

GND










GPIO_99

35

36

GPIO_16

SPI5_SCLK

UART5_TX







UART5_RX

SPI5_CS_N

GPIO_17

37

38

GPIO_101










GND

39

40

GPIO_100









# GPIO Test ## Hardware Connection Take Pin13 (GPIO_31) as an example: connect the positive probe of the multimeter to Pin13, and the negative probe to GND (such as Pin14). When testing other GPIOs, simply change the line offset in the command to the corresponding GPIO number. ## Test Method After entering adb shell, use the gpiod command to test GPIO. For regular SoC GPIOs, use gpiochip0, and GPIO_31 corresponds to line 31 of gpiochip0. ```bash adb shell # View the GPIO controller gpiodetect # Check the status of the specified GPIO gpioinfo gpiochip0 | grep -E "line[[:space:]]+31:" # Read the current GPIO level gpioget gpiochip0 31 # Pull the GPIO high and hold for 10 seconds. gpioset --mode=time --sec=10 gpiochip0 31=1 # Pull the GPIO low and hold for 10 seconds. gpioset --mode=time --sec=10 gpiochip0 31=0 ``` **GPIO interrupt test method:** Connect the GPIO under test to a key or an external signal source to make the pin level change; use gpiomon to monitor the rising edge and falling edge events of this GPIO. Here, GPIO_31 is still taken as an example. ```bash # Check the current level of GPIO_31 gpioget gpiochip0 31 # Monitor the rising edge and falling edge events of GPIO_31, and exit after being triggered 5 times. gpiomon --num-events=5 --rising-edge --falling-edge gpiochip0 31 ``` Note: If an error occurs `gpiodetect: error while loading shared libraries: libgpiod.so.2: cannot open shared object file: No such file or directory`,Execute first `export LD_LIBRARY_PATH=/opt/qcom/lib:$LD_LIBRARY_PATH` and then run the gpiod command. **Multimeter Measurement Results:** If the measured voltage is close to 3.3V when a high-level output is executed, and close to 0V when a low-level output is executed, the GPIO output function can be determined to be normal. ```{image} images/image_K1aEbuZ8so4L4ixXRmucerPWnic.webp :width: 1671px :height: 1246px :align: center ``` # I2C Test Pins 3 and 5 on the 40‑pin header are by default the I2C data and clock pins. To test the I2C interface, we need to connect an external I2C device. Here we use [the Waveshare environmental sensor expansion board](), and its corresponding device node is `/dev/i2c-1`. ## Test Preparation | **

IC
** | **

I2C Addr
** | **

ID Reg
** | **

Expected ID Return Value:
** | | --- | --- | --- | --- | |

WSL25911FN
|

0x29
|

0x12
|

0x50
| |

BME280
|

0x76
|

0xD0
|

0x60
| |

MPU9250
|

0x68
|

0x75
|

0x71
| |

LTR390-UV-1
|

0x53
|

0x06
|

0xB2
| PS: For the WSL25911FN used here, the command bit (0xA0) needs to be included when reading the ID register. That is, when accessing register 0x12, it is actually written as 0xA0 | 0x12 = 0xB2; no other registers require this. This test uses the Waveshare environmental sensor expansion board, which is connected via the 40‑pin header. **Hardware Connection Diagram:** ```{image} images/image_YNMhbOJNcojDt9xXItacfBPqnpe.webp :width: 1189px :height: 639px ``` Quectel Pi M1 connected to the expansion board ## Test Procedure ```plaintext adb shell # Enter the ADB shell ls -al /dev/i2c* # Check whether I2C-1 exists in the corresponding directory i2cdetect -y 1 # Check how many devices are attached to the corresponding I2C bus ``` ```{image} images/image_QhSNbPtSJoINwsxGeiAcYjWendL.webp :width: 526px :height: 339px :align: center ``` ## I2C Read/Write ```plaintext i2cget -y 1 0x53 0x05 b # Read the value of register 0x05 at device address 0x53 on i2c‑1, size one byte i2cset -y 1 0x53 0x05 0x03 b # Set the value of register 0x05 at device address 0x53 on i2c‑1 to 0x03, size one byte ``` ```{image} images/image_HjoqbFj7UohLv2xaU0HcTCXknMh.webp :width: 364px :height: 112px :align: center ``` # SPI Test Here we use [the 2.23-inch OLED expansion board](). The device chip-select nodes for the SPI function on the 40‑pin header are `/dev/spidev0.0` and `/dev/spidev0.1`. To view these nodes, enter the following in adb: ```bash adb shell # Enter the ADB shell ls /dev/spidev* # View SPI devices ``` ## Waveshare OLED display test (CS0 and CS1) Please connect the wiring according to the following table: | **OLED Pin** | **Connect To** | **SC200U Physical Pin** | **Remarks** | | --- | --- | --- | --- | | VCC | 3.3V power | Pin1 or Pin17 | Must be 3.3V | | GND | Ground | Any GND pin (e.g., Pin6, Pin9, Pin14) | Common ground | | DIN/MOSI | SPI data | Pin19 | SPI_MOSI | | CLK/SCK | SPI clock | Pin23 | SPI_CLK | | CS/CE | Chip select | Pin24/Pin26 | SPI_CE0/SPI_CE1 | | D/C | Data/Command | Pin22 | GPIO80 | | RES/RST | Reset | Pin18 | M1: GPIO25
L1: GPIO32 | **Hardware Connection Diagram:** ```{image} images/image_GCbKbLxOuo5T5sxwakVc2Nrcntg.webp :width: 1137px :height: 645px ```
SPI0.0 (CE0, Pin24) Test - OLED displays "ANDROID SPI0.0"
```{image} images/image_H09Hb1vtHoKXFbxrzzJc483Kn0f.webp :width: 1176px :height: 646px ```
SPI0.1 (CE1, Pin26) Test - OLED displays "ANDROID SPI0.1"
**On the Linux host, create the SPI test file spi_oled_demo.c:** ```cpp #include #include #include #include #include #include #include #include #include #define GPIO_BASE 385 #define GPIO_RST (GPIO_BASE + 32) #define GPIO_DC (GPIO_BASE + 80) // 大多数 SSD1306/SH1106 SPI OLED 使用 MODE0,先改为 MODE0,降速到 4MHz 提高可靠性 #define SPI_MODE SPI_MODE_0 #define SPI_SPEED 4000000 #define OLED_WIDTH 128 #define OLED_HEIGHT 32 #define OLED_PAGES 4 // Complete 5x7 font static const uint8_t font_5x7[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00}, // Space {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 {0x42, 0x61, 0x51, 0x49, 0x46}, // 2 {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 {0x27, 0x45, 0x45, 0x45, 0x39}, // 5 {0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6 {0x01, 0x71, 0x09, 0x05, 0x03}, // 7 {0x36, 0x49, 0x49, 0x49, 0x36}, // 8 {0x06, 0x49, 0x49, 0x29, 0x1E}, // 9 {0x7E, 0x11, 0x11, 0x11, 0x7E}, // A {0x7F, 0x49, 0x49, 0x49, 0x36}, {0x3E, 0x41, 0x41, 0x41, 0x22}, // B C {0x7F, 0x41, 0x41, 0x22, 0x1C}, {0x7F, 0x49, 0x49, 0x49, 0x41}, // D E {0x7F, 0x09, 0x09, 0x09, 0x01}, {0x3E, 0x41, 0x49, 0x49, 0x7A}, // F G {0x7F, 0x08, 0x08, 0x08, 0x7F}, {0x00, 0x41, 0x7F, 0x41, 0x00}, // H I {0x20, 0x40, 0x41, 0x3F, 0x01}, {0x7F, 0x08, 0x14, 0x22, 0x41}, // J K {0x7F, 0x40, 0x40, 0x40, 0x40}, {0x7F, 0x02, 0x0C, 0x02, 0x7F}, // L M {0x7F, 0x04, 0x08, 0x10, 0x7F}, {0x3E, 0x41, 0x41, 0x41, 0x3E}, // N O {0x7F, 0x09, 0x09, 0x09, 0x06}, {0x3E, 0x41, 0x51, 0x21, 0x5E}, // P Q {0x7F, 0x09, 0x19, 0x29, 0x46}, {0x46, 0x49, 0x49, 0x49, 0x31}, // R S {0x01, 0x01, 0x7F, 0x01, 0x01}, {0x3F, 0x40, 0x40, 0x40, 0x3F}, // T U {0x1F, 0x20, 0x40, 0x20, 0x1F}, {0x3F, 0x40, 0x38, 0x40, 0x3F}, // V W {0x63, 0x14, 0x08, 0x14, 0x63}, {0x07, 0x08, 0x70, 0x08, 0x07}, // X Y {0x61, 0x51, 0x49, 0x45, 0x43}, // Z {0x08, 0x08, 0x08, 0x08, 0x08}, // - {0x00, 0x36, 0x36, 0x00, 0x00}, // : {0x00, 0x60, 0x60, 0x00, 0x00}, // . }; static int char_to_index(char c) { if (c == ' ') return 0; if (c >= '0' && c <= '9') return 1 + (c - '0'); if (c >= 'A' && c <= 'Z') return 11 + (c - 'A'); if (c >= 'a' && c <= 'z') return 11 + (c - 'a'); if (c == '-') return 37; if (c == ':') return 38; if (c == '.') return 39; return 0; } int gpio_write(int gpio, int value); void oled_quick_test(const char *device, const char *name); int main() { printf("\n╔════════════════════════════════════════════════════════╗\n"); printf("║ Chip Select Pin Comparison Tool ║\n"); printf("╚════════════════════════════════════════════════════════╝\n"); printf("\nThis tool helps identify which CS pin is connected.\n"); printf("\nInstruction:\n"); printf(" 1. Connect OLED CS to Pin24 first\n"); printf(" 2. Run test - should see display\n"); printf(" 3. Move OLED CS to Pin26\n"); printf(" 4. Run test again - should see display\n"); printf("\n"); char choice; printf("Which pin is your OLED CS currently connected to?\n"); printf(" [0] Pin24 (CE0)\n"); printf(" [1] Pin26 (CE1)\n"); printf("Choice: "); scanf(" %c", &choice); if (choice == '0') { printf("\nTesting CE0 (Pin24)...\n"); oled_quick_test("/dev/spidev0.0", "CE0-PIN24"); } else if (choice == '1') { printf("\nTesting CE1 (Pin26)...\n"); oled_quick_test("/dev/spidev0.1", "CE1-PIN26"); } else { printf("Invalid choice\n"); return 1; } return 0; } void oled_quick_test(const char *device, const char *name) { // Display buffer static uint8_t buffer[OLED_PAGES][OLED_WIDTH]; // Simple GPIO functions int gpio_export(int gpio) { int fd = open("/sys/class/gpio/export", O_WRONLY); if (fd < 0) return -1; char buf[10]; snprintf(buf, sizeof(buf), "%d", gpio); write(fd, buf, strlen(buf)); close(fd); usleep(100000); return 0; } int gpio_set_dir(int gpio) { char path[50]; snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", gpio); int fd = open(path, O_WRONLY); if (fd < 0) return -1; write(fd, "out", 3); close(fd); return 0; } int gpio_write(int gpio, int value) { char path[50]; snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", gpio); int fd = open(path, O_WRONLY); if (fd < 0) return -1; char buf[2] = {value ? '1' : '0', 0}; write(fd, buf, 1); close(fd); return 0; } int configure_spi(int fd) { uint8_t mode = SPI_MODE; uint32_t speed = SPI_SPEED; if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) { perror(" ERROR: Cannot set SPI mode"); return -1; } if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) { perror(" ERROR: Cannot set SPI speed"); return -1; } printf(" SPI configured: mode %u, speed %u Hz\n", mode, speed); return 0; } void send_cmd(int fd, uint8_t cmd) { gpio_write(GPIO_DC, 0); write(fd, &cmd, 1); usleep(1000); } void set_pixel(int x, int y, int color) { if (x >= 0 && x < OLED_WIDTH && y >= 0 && y < OLED_HEIGHT) { int page = y / 8; int bit = y % 8; if (color) buffer[page][x] |= (1 << bit); else buffer[page][x] &= ~(1 << bit); } } void draw_char(int x, int y, char c) { int idx = char_to_index(c); for (int col = 0; col < 5; col++) { uint8_t data = font_5x7[idx][col]; for (int row = 0; row < 8; row++) { if (data & (1 << row)) { set_pixel(x + col, y + row, 1); } } } } void draw_text(int x, int y, const char *text) { int cursor = x; while (*text) { draw_char(cursor, y, *text); cursor += 6; text++; } } void clear_buffer() { memset(buffer, 0, OLED_PAGES * OLED_WIDTH); } void display_buffer(int fd) { for (int page = 0; page < OLED_PAGES; page++) { send_cmd(fd, 0xB0 + page); send_cmd(fd, 0x04); send_cmd(fd, 0x10); gpio_write(GPIO_DC, 1); write(fd, buffer[page], OLED_WIDTH); } } // Initialize GPIO gpio_export(GPIO_RST); gpio_export(GPIO_DC); usleep(200000); gpio_set_dir(GPIO_RST); gpio_set_dir(GPIO_DC); // Open SPI printf(" Opening %s...\n", device); int spi_fd = open(device, O_RDWR); if (spi_fd < 0) { perror(" ERROR: Cannot open device"); printf(" → %s is NOT working or OLED not connected\n", name); return; } printf(" ✓ Device opened\n"); // Configure SPI if (configure_spi(spi_fd) < 0) { printf(" → %s SPI configuration failed\n", name); close(spi_fd); return; } // Reset OLED printf(" Resetting OLED...\n"); gpio_write(GPIO_RST, 0); usleep(50000); gpio_write(GPIO_RST, 1); usleep(50000); // Initialize OLED printf(" Initializing OLED...\n"); send_cmd(spi_fd, 0xAE); send_cmd(spi_fd, 0x04); send_cmd(spi_fd, 0x10); send_cmd(spi_fd, 0x40); send_cmd(spi_fd, 0x81); send_cmd(spi_fd, 0xFF); send_cmd(spi_fd, 0xA1); send_cmd(spi_fd, 0xA6); send_cmd(spi_fd, 0xA8); send_cmd(spi_fd, 0x1F); send_cmd(spi_fd, 0xC8); send_cmd(spi_fd, 0xD3); send_cmd(spi_fd, 0x00); send_cmd(spi_fd, 0xD5); send_cmd(spi_fd, 0xF0); send_cmd(spi_fd, 0xD8); send_cmd(spi_fd, 0x05); send_cmd(spi_fd, 0xD9); send_cmd(spi_fd, 0xC2); send_cmd(spi_fd, 0xDA); send_cmd(spi_fd, 0x12); send_cmd(spi_fd, 0xDB); send_cmd(spi_fd, 0x08); send_cmd(spi_fd, 0xAF); printf(" ✓ OLED initialized\n"); // Test 1: Display "USING: CE0" or "USING: CE1" printf("\n [Test 1] Displaying chip select info...\n"); clear_buffer(); draw_text(10, 4, "USING:"); if (strstr(name, "CE0")) { draw_text(25, 16, "CE0"); } else { draw_text(25, 16, "CE1"); } display_buffer(spi_fd); printf(" ✓ Screen showing: USING %s\n", strstr(name, "CE0") ? "CE0" : "CE1"); sleep(3); // Test 2: Fill white printf("\n [Test 2] Filling screen WHITE...\n"); memset(buffer, 0xFF, OLED_PAGES * OLED_WIDTH); display_buffer(spi_fd); printf(" ✓ Screen should be WHITE\n"); sleep(2); // Test 3: Blink test printf("\n [Test 3] Blink test (5 times)...\n"); for (int i = 0; i < 5; i++) { // White memset(buffer, 0xFF, OLED_PAGES * OLED_WIDTH); display_buffer(spi_fd); usleep(200000); // Black memset(buffer, 0x00, OLED_PAGES * OLED_WIDTH); display_buffer(spi_fd); usleep(200000); printf(" Blink %d/5\n", i + 1); } printf(" ✓ Blink test complete\n"); // Test 4: FPS Performance Test printf("\n [Test 4] FPS Performance Test...\n"); printf(" Running 100 frame updates...\n"); struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); for (int frame = 0; frame < 100; frame++) { // Generate test pattern for (int page = 0; page < OLED_PAGES; page++) { for (int x = 0; x < OLED_WIDTH; x++) { buffer[page][x] = (x + frame) & 0xFF; } } display_buffer(spi_fd); } clock_gettime(CLOCK_MONOTONIC, &end); double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; double fps = 100.0 / elapsed; printf(" ✓ Performance: %.1f FPS\n", fps); // Display FPS result on screen clear_buffer(); draw_text(10, 4, "FPS TEST"); char fps_str[20]; snprintf(fps_str, sizeof(fps_str), "%.1f FPS", fps); draw_text(20, 16, fps_str); display_buffer(spi_fd); printf(" ✓ FPS result displayed on screen\n"); sleep(3); // Test 5: CS Pin Functionality Test printf("\n [Test 5] CS Pin Functionality Test...\n"); printf(" This test verifies CS pin actually controls the device\n"); // Step 1: Display number 1 printf(" Step 1: Displaying pattern 1...\n"); clear_buffer(); draw_text(15, 4, "PATTERN"); draw_text(50, 16, "1"); display_buffer(spi_fd); sleep(2); // Step 2: Try to update through the OTHER CS (should fail if CS works) printf(" Step 2: Trying to update via OTHER CS...\n"); const char *other_device = strstr(name, "CE0") ? "/dev/spidev0.1" : "/dev/spidev0.0"; const char *other_name = strstr(name, "CE0") ? "CE1" : "CE0"; int other_fd = open(other_device, O_RDWR); if (other_fd >= 0) { if (configure_spi(other_fd) < 0) { close(other_fd); printf(" Skipping %s update because SPI configuration failed\n", other_name); goto update_back; } // Try to display pattern 2 via other CS clear_buffer(); draw_text(15, 4, "PATTERN"); draw_text(50, 16, "2"); for (int page = 0; page < OLED_PAGES; page++) { send_cmd(other_fd, 0xB0 + page); send_cmd(other_fd, 0x04); send_cmd(other_fd, 0x10); gpio_write(GPIO_DC, 1); write(other_fd, buffer[page], OLED_WIDTH); } close(other_fd); printf(" Sent update via %s (other CS)\n", other_name); sleep(2); printf("\n ╔════════════════════════════════════════════════╗\n"); printf(" ║ CHECK SCREEN: Still showing '1' or changed to '2'? ║\n"); printf(" ╚════════════════════════════════════════════════╝\n"); printf("\n If still showing '1': CS pin is WORKING correctly ✓\n"); printf(" If changed to '2': CS pin NOT working (both CS active) ✗\n\n"); sleep(2); } // Step 3: Update back via correct CS update_back: printf(" Step 3: Updating via correct CS (%s)...\n", name); clear_buffer(); draw_text(15, 4, "BACK TO"); draw_text(50, 16, "1"); display_buffer(spi_fd); printf(" ✓ Should see pattern 1 again\n"); sleep(2); // Test 6: FPS result summary printf("\n [Test 6] Final Summary...\n"); clear_buffer(); draw_text(30, 4, name); char summary_str[20]; snprintf(summary_str, sizeof(summary_str), "%.1fFPS", fps); draw_text(20, 16, summary_str); display_buffer(spi_fd); printf(" ✓ Summary displayed\n"); sleep(3); // Clear screen clear_buffer(); display_buffer(spi_fd); close(spi_fd); printf("\n======================================================================\n"); printf(" %s Test Complete!\n", name); printf("======================================================================\n"); printf(" CS Pin Test Result:\n"); printf(" If screen didn't change to '2' in Step 2:\n"); printf(" → CS pin is working correctly ✓\n"); printf(" If screen changed to '2':\n"); printf(" → CS pin might not be controlling the device ✗\n"); printf(" → Both CS pins may be active simultaneously\n"); printf("\n Performance: %.1f FPS\n", fps); printf("======================================================================\n"); } ``` **After the file is created, compile it into an executable:** ```plaintext mkdir -p out # Create the output directory aarch64-linux-gnu-gcc spi_oled_demo.c -O2 -Wall -Wextra -o spi_oled_demo # Compile into an executable ``` Copy the compiled file from the Linux environment to the same directory as the ADB shell. **Execution steps:** ```bash adb push spi_oled_demo /tmp/spi_oled_demo adb shell chmod +x /tmp/spi_oled_demo adb shell /tmp/spi_oled_demo ``` After the program runs, it will prompt you to select the SPI device: - **Select 0:** use `/dev/spidev0.0` (CE0, Pin24) - **Select 1:** use `/dev/spidev0.1` (CE1, Pin26) Please select the corresponding device according to your actual wiring (if the CS/CE pin is connected to pin24, select 0; if connected to pin26, select 1). **Test results:** After execution, the program will perform the following tests in sequence: ```yaml ╔══════════════════════════════════════════════════════╗ ║ Android SPI OLED Test Program ║ ║ For Android Devices ║ ╚══════════════════════════════════════════════════════╝ Select SPI device: [0] /dev/spidev0.0 (CE0, Pin24) [1] /dev/spidev0.1 (CE1, Pin26) [c] Custom device path Selection: 1 Using device: /dev/spidev0.1 Initializing GPIO... ✓ GPIO initialization successful Opening SPI device: /dev/spidev0.1 ✓ SPI device opened and configured - Mode: 3 - Speed: 2000000 Hz - Bits per word: 8 bits Initializing OLED... Resetting OLED... Sending initialization commands... ✓ OLED initialization complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Starting test sequence: SPI0.1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [Test 1] Display device info... ✓ Screen should display: ANDROID SPI0.1 [Test 2] Full white screen test... ✓ Screen should be fully white [Test 3] Blink test (5 times)... Blink 1/5 Blink 2/5 Blink 3/5 Blink 4/5 Blink 5/5 ✓ Blink test complete [Test 4] FPS performance test... Running 100 frame updates... ✓ Performance: 40.3 FPS (elapsed: 2.48 seconds) ✓ FPS result displayed [Test 5] Pattern test... Pattern 1: Horizontal stripes Pattern 2: Vertical stripes Pattern 3: Checkerboard ✓ Pattern test complete [Test 6] Test summary... ✓ Summary displayed ``` Test Descriptions: - Test 1: Verifies that the OLED can display text information normally. - Test 2: Verifies the OLED full‑screen display function. - Test 3: Verifies the OLED refresh function through the blinking test. - Test 4: Tests SPI communication performance (FPS value). - Test 5: Tests the display of different patterns. - Test 6: Displays the test summary information. If all test items show a ✓ mark, it indicates that both SPI communication and the OLED function are working properly. # UART Test On the **40‑pin** header, **pin8 and 10** are configured by default as **UART** functions, with the corresponding device node being **/dev/ttyHS0**. **Viewing Serial Devices** You can use the following command to view all serial devices in the system: ```plaintext ls /dev/tty* ``` ## UART Loopback Test This test verifies whether the UART transmit and receive functions are working properly by shorting **pins 8 and 10** together. **Hardware Connection:** Short pin 8 (TX) and pin 10 (RX) on the 40‑pin header. **Create the UART test file on the Linux host:** Create a new file named `uart_loopback.c` with the following content: ```cpp #include #include #include #include #include #include #include #include static int serial_fd = -1; static volatile int running = 1; void signal_handler(int sig) { printf("\n用户中断测试\n"); running = 0; if (serial_fd >= 0) { close(serial_fd); printf("串口已关闭\n"); } exit(0); } int configure_serial(int fd, int baudrate) { struct termios tty; if (tcgetattr(fd, &tty) != 0) { printf("获取串口属性失败: %s\n", strerror(errno)); return -1; } speed_t speed; switch (baudrate) { case 9600: speed = B9600; break; case 19200: speed = B19200; break; case 38400: speed = B38400; break; case 57600: speed = B57600; break; case 115200: speed = B115200; break; case 230400: speed = B230400; break; default: speed = B115200; break; } cfsetospeed(&tty, speed); cfsetispeed(&tty, speed); tty.c_cflag &= ~PARENB; tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &= ~CRTSCTS; tty.c_cflag |= CREAD | CLOCAL; tty.c_iflag &= ~(IXON | IXOFF | IXANY); tty.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG); tty.c_oflag &= ~OPOST; tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); tty.c_cc[VTIME] = 10; tty.c_cc[VMIN] = 0; if (tcsetattr(fd, TCSANOW, &tty) != 0) { printf("设置串口属性失败: %s\n", strerror(errno)); return -1; } return 0; } int serial_loopback_test(const char* port, int baudrate) { char test_data[] = "Hello, Serial Loopback!"; char received_data[256]; ssize_t bytes_written, bytes_read; serial_fd = open(port, O_RDWR | O_NOCTTY | O_SYNC); if (serial_fd < 0) { printf("无法打开串口 %s: %s\n", port, strerror(errno)); return -1; } if (configure_serial(serial_fd, baudrate) != 0) { close(serial_fd); return -1; } printf("串口 %s 已打开,开始回环测试(按Ctrl+C退出)...\n", port); while (running) { tcflush(serial_fd, TCIOFLUSH); bytes_written = write(serial_fd, test_data, strlen(test_data)); if (bytes_written < 0) { printf("发送数据失败: %s\n", strerror(errno)); break; } printf("发送: %s\n", test_data); usleep(100000); bytes_read = read(serial_fd, received_data, sizeof(received_data) - 1); if (bytes_read < 0) { printf("读取数据失败: %s\n", strerror(errno)); break; } received_data[bytes_read] = '\0'; if (bytes_read == (ssize_t)strlen(test_data) && strncmp(received_data, test_data, strlen(test_data)) == 0) { printf("接收: %s → 测试通过\n\n", received_data); } else { printf("接收异常: 发送[%zu] vs 接收[%zd] → 测试失败\n", strlen(test_data), bytes_read); if (bytes_read > 0) { printf("接收内容: %s\n\n", received_data); } else { printf("未接收到数据\n\n"); } } sleep(1); } close(serial_fd); printf("串口 %s 已关闭\n", port); return 0; } void print_usage(const char* prog) { printf("UART回环测试程序\n\n"); printf("用法:\n"); printf(" %s -n [-b 波特率] 指定ttyHS编号\n", prog); printf(" %s -d <设备路径> [-b 波特率] 指定完整设备路径\n", prog); printf(" %s 默认使用 /dev/ttyHS5 @ 115200\n\n", prog); printf("示例:\n"); printf(" %s -n 3 使用 /dev/ttyHS3 @ 115200\n", prog); printf(" %s -n 5 -b 9600 使用 /dev/ttyHS5 @ 9600\n", prog); printf(" %s -d /dev/ttyUSB0 使用 /dev/ttyUSB0 @ 115200\n", prog); } int main(int argc, char* argv[]) { char port_buf[64]; const char* port = "/dev/ttyHS5"; int baudrate = 115200; int opt; while ((opt = getopt(argc, argv, "n:d:b:h")) != -1) { switch (opt) { case 'n': snprintf(port_buf, sizeof(port_buf), "/dev/ttyHS%s", optarg); port = port_buf; break; case 'd': port = optarg; break; case 'b': baudrate = atoi(optarg); break; case 'h': default: print_usage(argv[0]); return (opt == 'h') ? 0 : 1; } } signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); printf("UART回环测试程序\n"); printf("使用串口: %s\n", port); printf("波特率: %d\n", baudrate); printf("=====================================\n"); return serial_loopback_test(port, baudrate); } ``` **After the file is created, compile it into an executable:** ```plaintext mkdir -p out # Create the output directory aarch64-linux-gnu-gcc uart_loopback.c -O2 -Wall -Wextra -o out/uart_loopback # Compile into an executable ``` Copy the compiled file from the Linux environment to the same directory as the ADB shell. **Execution steps:** ```bash adb push uart_loopback /tmp/uart_loopback adb shell chmod +x /tmp/uart_loopback adb shell /tmp/uart_loopback /dev/ttyHS1 ``` **Test results:** When pins 8 and 10 are correctly shorted, the program will continuously send data and verify whether the received data matches. ```plaintext Using serial port: /dev/ttyHS1 Baud rate: 115200 ===================================== Serial port /dev/ttyHS1 opened, starting loopback test (Press Ctrl+C to exit)... Sent: Hello, Serial Loopback! Received: Hello, Serial Loopback! → Test passed Sent: Hello, Serial Loopback! Received: Hello, Serial Loopback! → Test passed Sent: Hello, Serial Loopback! Received: Hello, Serial Loopback! → Test passed ``` Press `Ctrl+C` to exit the test program. The program will automatically close the serial port and exit. # Temperature‑Controlled Fan Test A background process monitors /sys/class/thermal/thermal_zone*, periodically reads the temperatures of several CPU cores, and takes the highest value. Based on this highest temperature, it configures the fan’s PWM duty cycle. | **

CPU Temperature
** | **

Duty Cycle / 255
** | | --- | --- | |

<30℃
|

0
| |

30~40℃
|

64
| |

40~50℃
|

128
| |

50~60℃
|

192
| |

>60℃
|

255
| ## Wiring Diagram This test uses a [Raspberry Pi fan](). Connect it to the corresponding pins on the M1/L1 40‑pin header as shown in the diagram below. | **Fan Pin** | **M1 Development Board Pin (40‑PIN)** | | --- | --- | | Red wire (Power positive) | PIN2 | | Black wire (Power negative) | PIN6 | | Blue wire (PWM) | PIN33 | | Yellow wire (Speed detection / tachometer) | Not connected | ```{image} images/image_RXQhb9hGNoUxO1xlx7Bcr23PnNg.webp :width: 756px :height: 587px :align: center ``` ## Test Procedure 1. By default, the fan function is disabled. After power‑on, enter the following commands in adb to check the status of the fan service. ```plaintext adb shell 40pin-ctrl fan status # View the current status of the fan service (bound if not enabled, enable if active) 40pin-ctrl fan enable # Enable the fan service; after enabling, you can check whether the status shows "enable" 40pin-ctrl fan disable # Disable the fan service (only needed to stop the test) ``` 2. Enter the following in the first terminal. ```cpp 40pin-ctrl fan enable 40pin-ctrl fan status #M1 watch -n 1 'PWM=/sys/bus/platform/devices/1c40000.qcom,spmi:qcom,pm6125@1:qcom,pwms@b300/pwm/pwmchip0/pwm0; p=$(cat "$PWM/period"); d=$(cat "$PWM/duty_cycle"); echo $(( (d * 255 + p / 2) / p ))' #L1 watch -n 1 'PWM=/sys/bus/platform/devices/1c40000.qcom,spmi:qcom,pm2250@1:qcom,pwms@be00/pwm/pwmchip2/pwm0; p=$(cat "$PWM/period"); d=$(cat "$PWM/duty_cycle"); echo $(( (d * 255 + p / 2) / p ))' ``` Check whether the current temperature and PWM duty cycle match. 3. Open a second terminal and perform the heating (temperature‑raising) operation. ```plaintext for i in 1 2 3 4; do yes > /dev/null & done ``` 4. If the temperature rise in step 3 is insufficient, you can increase the load. ```plaintext for i in $(seq 1 $(nproc)); do yes > /dev/null & done ``` Check whether the temperatures and fan PWM duty cycles in the two terminals match the values in the table. ```{image} images/image_JMxnb7vx6ofOCRxluD3cYA4anoe.webp :width: 1832px :height: 859px :align: center ``` 5. After the test ends, stop the load. ```plaintext killall yes ```